#ifndef PORTABLE_LOGGING_H_
#define PORTABLE_LOGGING_H_

#include <iostream>
#include <type_traits>

#include "src/pbc/portable-macros.h"

namespace portable {

// Helper to determine how to pass values: Pass scalars and arrays by value,
// others by const reference (even if it was a non-const ref before; this is
// disallowed by the style guide anyway).
// The default is to also remove array extends (int[5] -> int*), but this can be
// disabled by setting {remove_array_extend} to false.
template <typename T, bool remove_array_extend = true>
struct pass_value_or_ref {
  using noref_t = typename std::remove_reference<T>::type;
  using decay_t = typename std::conditional<
      std::is_array<noref_t>::value && !remove_array_extend, noref_t,
      typename std::decay<noref_t>::type>::type;
  using type = typename std::conditional<std::is_scalar<decay_t>::value ||
                                             std::is_array<decay_t>::value,
                                         decay_t, const decay_t&>::type;
};

// Define the default implementation of Cmp##NAME##Impl to be used by
// CHECK##NAME##Impl.
// Note the specializations below for integral types with mismatching
// signedness.
#define P_DEFINE_CMP_IMPL(NAME, op)                 \
  template <typename Lhs, typename Rhs>             \
  AWINLINE bool Cmp##NAME##Impl(Lhs lhs, Rhs rhs) { \
    return lhs op rhs;                              \
  }
P_DEFINE_CMP_IMPL(EQ, ==)
P_DEFINE_CMP_IMPL(NE, !=)
P_DEFINE_CMP_IMPL(LE, <=)
P_DEFINE_CMP_IMPL(LT, <)
P_DEFINE_CMP_IMPL(GE, >=)
P_DEFINE_CMP_IMPL(GT, >)
#undef P_DEFINE_CMP_IMPL

// Define the implementation of Check##NAME##Impl, using Cmp##NAME##Impl defined
// above.
#define P_DEFINE_CHECK_OP_IMPL(NAME)                          \
  template <typename Lhs, typename Rhs>                       \
  AWINLINE bool Check##NAME##Impl(Lhs lhs, Rhs rhs) {         \
    using LhsPassT = typename pass_value_or_ref<Lhs>::type;   \
    using RhsPassT = typename pass_value_or_ref<Rhs>::type;   \
    bool cmp = Cmp##NAME##Impl<LhsPassT, RhsPassT>(lhs, rhs); \
    return cmp;                                               \
  }
P_DEFINE_CHECK_OP_IMPL(EQ)
P_DEFINE_CHECK_OP_IMPL(NE)
P_DEFINE_CHECK_OP_IMPL(LE)
P_DEFINE_CHECK_OP_IMPL(LT)
P_DEFINE_CHECK_OP_IMPL(GE)
P_DEFINE_CHECK_OP_IMPL(GT)
#undef P_DEFINE_CHECK_OP_IMPL

#define P_CHECK_OP(name, op, lhs, rhs)                                       \
  do {                                                                       \
    if (!Check##name##Impl<typename pass_value_or_ref<decltype(lhs)>::type,  \
                           typename pass_value_or_ref<decltype(rhs)>::type>( \
            (lhs), (rhs))) {                                                 \
      std::cout << "CHECK FAILED: " << #lhs #op #rhs << std::endl;           \
      std::cout << "          IN: " << __FILE__ << ":" << __LINE__ << ":"    \
                << __FUNCTION__ << std::endl;                                \
      std::abort();                                                          \
    }                                                                        \
  } while (false)

#define P_CHECK(expr)                                                     \
  do {                                                                    \
    if (P_UNLIKELY(!(expr))) {                                            \
      std::cout << "CHECK FAILED: " << #expr << std::endl;                \
      std::cout << "          IN: " << __FILE__ << ":" << __LINE__ << ":" \
                << __FUNCTION__ << std::endl;                             \
      std::abort();                                                       \
    }                                                                     \
  } while (false)

#define P_DCHECK_WITH_MSG(condition, message)                             \
  do {                                                                    \
    if (P_UNLIKELY(!(condition))) {                                       \
      std::cout << "CHECK FAILED: " << message << std::endl;              \
      std::cout << "          IN: " << __FILE__ << ":" << __LINE__ << ":" \
                << __FUNCTION__ << std::endl;                             \
      std::abort();                                                       \
    }                                                                     \
  } while (false)

#define P_CHECK_WITH_MSG(condition, message)                              \
  do {                                                                    \
    if (P_UNLIKELY(!(condition))) {                                       \
      std::cout << "CHECK FAILED: " << message;                           \
      std::cout << "          IN: " << __FILE__ << ":" << __LINE__ << ":" \
                << __FUNCTION__ << std::endl;                             \
      std::abort();                                                       \
    }                                                                     \
  } while (false)

#define P_FATAL(message)                                                       \
  std::cout << "FATAL: " << message;                                           \
  std::cout << "   IN: " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ \
            << std::endl;                                                      \
  std::abort();

// The DCHECK macro is equivalent to CHECK except that it only
// generates code in debug builds.
#ifdef DEBUG
#define P_DCHECK(val) P_CHECK(val)
#define P_DCHECK_EQ(lhs, rhs) P_CHECK_OP(EQ, ==, lhs, rhs)
#define P_DCHECK_NE(lhs, rhs) P_CHECK_OP(NE, !=, lhs, rhs)
#define P_DCHECK_GT(lhs, rhs) P_CHECK_OP(GT, >, lhs, rhs)
#define P_DCHECK_GE(lhs, rhs) P_CHECK_OP(GE, >=, lhs, rhs)
#define P_DCHECK_LT(lhs, rhs) P_CHECK_OP(LT, <, lhs, rhs)
#define P_DCHECK_LE(lhs, rhs) P_CHECK_OP(LE, <=, lhs, rhs)
#define P_DCHECK_NULL(val) P_CHECK((val) == nullptr)
#define P_DCHECK_NOT_NULL(val) P_CHECK((val) != nullptr)
#define P_DCHECK_IMPLIES(lhs, rhs) \
  P_DCHECK_WITH_MSG(!(lhs) || (rhs), #lhs " implies " #rhs)
#define P_ABORT_AND_UNREACHABLE()                                            \
  do {                                                                       \
    std::cout << "This line should be unreachable: " << std::endl;           \
    std::cout << "   " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ \
              << std::endl;                                                  \
    std::abort();                                                            \
    __builtin_unreachable();                                                 \
  } while (0)

#define P_UNREACHABLE() P_ABORT_AND_UNREACHABLE()
#else
#define P_DCHECK(condition) ((void)0)
#define P_DCHECK_EQ(v1, v2) ((void)0)
#define P_DCHECK_NE(v1, v2) ((void)0)
#define P_DCHECK_GT(v1, v2) ((void)0)
#define P_DCHECK_GE(v1, v2) ((void)0)
#define P_DCHECK_LT(v1, v2) ((void)0)
#define P_DCHECK_LE(v1, v2) ((void)0)
#define P_DCHECK_NULL(val) ((void)0)
#define P_DCHECK_NOT_NULL(val) ((void)0)
#define P_DCHECK_IMPLIES(v1, v2) ((void)0)

#define P_UNREACHABLE()                                                      \
  do {                                                                       \
    __builtin_unreachable();                                                 \
  } while (0)
#endif

#define P_CHECK_EQ(lhs, rhs) P_CHECK_OP(EQ, ==, lhs, rhs)
#define P_CHECK_NE(lhs, rhs) P_CHECK_OP(NE, !=, lhs, rhs)
#define P_CHECK_LE(lhs, rhs) P_CHECK_OP(LE, <=, lhs, rhs)
#define P_CHECK_LT(lhs, rhs) P_CHECK_OP(LT, <, lhs, rhs)
#define P_CHECK_GE(lhs, rhs) P_CHECK_OP(GE, >=, lhs, rhs)
#define P_CHECK_GT(lhs, rhs) P_CHECK_OP(GT, >, lhs, rhs)
#define P_CHECK_NULL(val) P_CHECK((val) == nullptr)
#define P_CHECK_NOT_NULL(val) P_CHECK((val) != nullptr)

}  // namespace portable

#endif  // PORTABLE_LOGGING_H_
